﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;

using Data.GameData.Interfaces;
using Data.GameData.Entities;
using Data.GameData.Utils;

namespace Data.GameData
{
    public class Race : GameObject
    {
        #region Public Event Handlers
        // Declare the event using EventHandler<T>
        public static event EventHandler<BasicEventArgs> RaiseActiveRaceChangedEvent;
        public static event EventHandler<BasicEventArgs> RaiseRaceChangedEvent;
        public static event EventHandler<BasicEventArgs> RaiseNewStarSystemEvent;
        public static event EventHandler<BasicEventArgs> RaiseNewOrderEvent;
        #endregion

        #region Attributes
        private ERace dbRace;
        private static Dictionary<Guid, Race> raceList = new Dictionary<Guid, Race>();
        private static Race activeRace;

        private Guid playerId;
        private string name;
        private Color colour;

        private int habIndex;
        private SystemBodyEnviroment perferedEnviroment;

        private int economicLevel;

        private bool initialTurn = true;
        private double monthlyMcTotal;
        private double maintenanceCosts;
        private bool turnComplete = false;

        ///
        /// Known Races
        /// Guid = RaceId
        private Dictionary<Guid, RaceRelation> knownRaces = new Dictionary<Guid, RaceRelation>();

        ///
        /// Known StarSystems
        /// Guid = StarSystemInformationId
        private Dictionary<Guid, StarSystemInformation> knownStarSystems = new Dictionary<Guid, StarSystemInformation>();

        ///
        /// Owned StarSystems
        /// Guid = StarSystemInformationId
        private Dictionary<Guid, StarSystemInformation> ownedStarSystems = new Dictionary<Guid, StarSystemInformation>();
        private StarSystemInformation homeSystem;

        ///
        /// Owned Populations
        /// Guid = SystemBodyId
        private Dictionary<Guid, Population> ownedPopulations = new Dictionary<Guid, Population>();

        // Owned Units : StarShips / Bases / Starbases
        private Dictionary<Guid, IFleetElement> fleets = new Dictionary<Guid, IFleetElement>();
        private Dictionary<Guid, Unit> bases = new Dictionary<Guid, Unit>();
        private Dictionary<Guid, Unit> starbases = new Dictionary<Guid, Unit>();

        private List<Unit> unitDesigns = new List<Unit>();

        //
        // Turn Orders
        private List<IOrder> orders = new List<IOrder>();
        #endregion

        #region Constructors
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="playerId"></param>
        public Race(string name, Guid playerId)
        {
            dbRace = new ERace();

            this.playerId = playerId;
            this.name = name;
            MarkNew();
        }
        
        public Race(Guid raceId)
        {
            dbRace = new ERace();

            if (raceId != Guid.Empty)
            {
                dbRace = ERace.LoadFromDB(raceId);
                Load();
            }
        }

        private Race(ERace entity)
        {
            this.dbRace = entity;
            Load();
        }
        #endregion

        #region Public Static Properties
        public static Race ActiveRace
        {
            get { return Race.activeRace; }
            set
            {
                Race.activeRace = value;
                if (value != null)
                    value.OnRaiseActiveRaceChangedEvent(new BasicEventArgs());
            }
        }

        static public void AddNewRace(Race race)
        {
            if (race == null)
                return;

            if (!raceList.ContainsKey(race.Id))
            {
                raceList.Add(race.Id, race);
            }
        }

        static public List<Race> Races
        {
            get
            {
                if (raceList == null || raceList.Count == 0) FetchRacesForGame();
                return new List<Race>(raceList.Values);
            }
        }

        static public Race GetRace(Guid gameId)
        {
            if (raceList == null || raceList.Count == 0) FetchRacesForGame();

            if (raceList.ContainsKey(gameId))
                return raceList[gameId];

            return null;
        }

        static public Race[] GetRaces()
        {
            return Races.ToArray();
        }
        #endregion

        #region Static Private Methods
        static private void FetchRacesForGame()
        {
            if (raceList == null || raceList.Count == 0)
            {
                raceList = new Dictionary<Guid, Race>();

                List<ERace> gameEntityList = ERace.GetAllForGame(Game.ActiveGame.Id);
                lock (raceList)
                {
                    // Create the Plan business objects
                    foreach (ERace gameEntity in gameEntityList)
                    {
                        Race obj = new Race(gameEntity);
                        raceList.Add(obj.Id, obj);
                    }
                }
            }
        }
        #endregion

        #region Properties
        public Guid PlayerId
        {
            get { return playerId; }
            protected set { playerId = value; }
        }

        public string Name
        {
            get { return name; }
            set
            {
                if (name == value) return;

                name = value;
                MarkDirty();
                OnRaiseRaceChangedEvent(new BasicEventArgs(Id, BasicEventType.eRACE, BasicOperationType.eMODIFY));
            }
        }

        public Color Colour
        {
            get { return colour; }
            set
            {
                if (colour == value) return;

                colour = value;
                MarkDirty();
                OnRaiseRaceChangedEvent(new BasicEventArgs(Id, BasicEventType.eRACE, BasicOperationType.eMODIFY));
            }
        }

        public int HabIndex
        {
            get { return habIndex; }
            set
            {
                if (habIndex == value) return;

                habIndex = value;
                MarkDirty();
            }
        }

        public SystemBodyEnviroment PerferedEnviroment
        {
            get { return perferedEnviroment; }
            set
            {
                if (perferedEnviroment == value) return;

                perferedEnviroment = value;
                MarkDirty();
            }
        }

        public int EconomicLevel
        {
            get { return economicLevel; }
            set
            {
                if (economicLevel == value) return;

                economicLevel = value;
                MarkDirty();
                OnRaiseRaceChangedEvent(new BasicEventArgs(Id, BasicEventType.eRACE, BasicOperationType.eMODIFY));
            }
        }

        public StarSystemInformation HomeSystem
        {
            get
            {
                if (homeSystem == null)
                {
                    List<StarSystemInformation> sysInfoList = StarSystemInformation.StarSystemInfo;
                    homeSystem = sysInfoList.FirstOrDefault(sys => sys.IsHomeSystem == true && sys.RaceId == Id && sys.OwnerId == Id);
                }
                return homeSystem;
            }
            set
            {
                if (homeSystem == value) return;

                homeSystem = value;
                MarkDirty();
            }
        }

        public bool TurnComplete
        {
            get { return turnComplete; }
            set
            {
                if (turnComplete == value) return;

                turnComplete = value;
                MarkDirty();
            }
        }

        public bool InitialTurn
        {
            get { return initialTurn; }
            set
            {
                if (initialTurn == value) return; 

                initialTurn = value;
                MarkDirty();
            }
        }
        #endregion

        #region KnownRaces
        public IEnumerable<RaceRelation> KnownRaces
        {
            get { return knownRaces.Values; }
        }

        public void AddKnownRace(RaceRelation raceInfo)
        {
            if (!knownRaces.ContainsKey(raceInfo.Race.Id))
            {
                knownRaces.Add(raceInfo.Race.Id, raceInfo);
                MarkDirty();
            }
        }
        #endregion

        #region KnownStarSystems
        public IEnumerable<StarSystemInformation> KnownStarSystems
        {
            get
            {
                if (knownStarSystems.Count == 0)
                {
                    List<StarSystemInformation> sysInfoList = StarSystemInformation.StarSystemInfo;
                    foreach (StarSystemInformation info in sysInfoList.Where(sys => sys.RaceId == Id))
                        knownStarSystems.Add(info.Id, info);
                }
                return knownStarSystems.Values;
            }
        }

        public void AddKnownStarSystem(StarSystemInformation systemInfo)
        {
            if (!knownStarSystems.ContainsKey(systemInfo.Id))
            {
                knownStarSystems.Add(systemInfo.Id, systemInfo);
                MarkDirty();
                if (systemInfo != null)
                    this.OnRaiseNewStarSystemEvent(new BasicEventArgs(Id, BasicEventType.eSTAR_SYSTEM, BasicOperationType.eADD));
            }
        }
        #endregion

        #region OwnedStarSystems
        public IEnumerable<StarSystemInformation> OwnedStarSystems
        {
            get
            {
                if (ownedStarSystems.Count == 0)
                {
                    List<StarSystemInformation> sysInfoList = StarSystemInformation.StarSystemInfo;
                    foreach (StarSystemInformation info in sysInfoList.Where(sys => sys.RaceId == Id && sys.OwnerId == Id))
                        ownedStarSystems.Add(info.Id, info);
                }
                return ownedStarSystems.Values;
            }
        }

        public void AddOwnedStarSystem(StarSystemInformation systemInfo)
        {
            if (!ownedStarSystems.ContainsKey(systemInfo.Id))
            {
                ownedStarSystems.Add(systemInfo.Id, systemInfo);
                AddKnownStarSystem(systemInfo);
                MarkDirty();
            }
        }
        #endregion

        #region OwnedPopulations
        public IEnumerable<Population> Populations
        {
            get { return ownedPopulations.Values; }
        }

        public void AddPopulation(Population popInfo)
        {
            if (!ownedPopulations.ContainsKey(popInfo.SettledBody.Id))
            {
                ownedPopulations.Add(popInfo.SettledBody.Id, popInfo);
                MarkDirty();
            }
        }

        public void RemovePopulation(Population pop)
        {
            if (ownedPopulations.Remove(pop.SettledBody.Id))
            {
                pop.MarkDeleted();
                MarkDirty();
            }
        }
        #endregion

        #region Fleets
        public IEnumerable<IFleetElement> Fleets
        {
            get { return fleets.Values; }
        }

        public void AddFleet(IFleetElement unit)
        {
            if (!fleets.ContainsKey(unit.Id))
            {
                fleets.Add(unit.Id, unit);
                MarkDirty();
            }
        }

        public void RemoveFleet(Unit unit)
        {
            if (fleets.Remove(unit.Id))
                MarkDirty();
        }
        #endregion

        #region Starbases
        public IEnumerable<Unit> Bases
        {
            get { return bases.Values; }
        }

        public void AddBase(Unit unit)
        {
            if (!bases.ContainsKey(unit.Id))
            {
                bases.Add(unit.Id, unit);
                MarkDirty();
            }
        }

        public void RemoveBase(Unit unit)
        {
            if (bases.Remove(unit.Id))
                MarkDirty();
        }
        #endregion

        #region Starbases
        public IEnumerable<Unit> Starbases
        {
            get { return starbases.Values; }
        }

        public void AddStarbase(Unit unit)
        {
            if (!starbases.ContainsKey(unit.Id))
            {
                starbases.Add(unit.Id, unit);
                MarkDirty();
            }
        }

        public void RemoveStarbase(Unit unit)
        {
            if (starbases.Remove(unit.Id))
                MarkDirty();
        }
        #endregion

        #region Unit Designs
        public void AddDesign(Unit design)
        {
            if (!unitDesigns.Contains(design))
            {
                unitDesigns.Add(design);
                MarkDirty();
            }
        }

        public void RemoveDesign(Unit design)
        {
            if (unitDesigns.Remove(design))
                MarkDirty();
        }

        public IEnumerable<Unit> GetDesigns()
        {
            return unitDesigns;
        }

        public IEnumerable<Unit> GetDesignsForClass(string unitClass)
        {
            return unitDesigns.Where(unit => unit.ShipClass.ShortClassName == unitClass);
        }
        #endregion

        #region Orders
        public double MonthlyMcTotal
        {
            get { return monthlyMcTotal; }
            set
            {
                if (monthlyMcTotal == value) return;
                monthlyMcTotal = value;
                MarkDirty();
            }
        }

        public double MaintenanceCosts
        {
            get { return maintenanceCosts; }
            set
            {
                if (maintenanceCosts == value) return;
                maintenanceCosts = value;
                MarkDirty();
            }
        }

        public IEnumerable<IOrder> Orders
        {
            get { return orders; }
        }

        public void AddOrder(IOrder order)
        {
            orders.Add(order);
            MarkDirty();
            OnRaiseNewOrderEvent(new BasicEventArgs(Id, BasicEventType.eORDER, BasicOperationType.eADD));
        }

        public void ClearCompletedOrders()
        {
            if (orders.RemoveAll(o => o.Status == OrderStatusType.COMPLETE) != 0)
                MarkDirty();
            OnRaiseNewOrderEvent(new BasicEventArgs(Id, BasicEventType.eORDER, BasicOperationType.eDELETE));
        }

        public double MonthlySpend()
        {
            double total = 0.0;
            foreach (Order order in orders.Where(o => o.TurnCreated == Game.ActiveGame.TurnNumber))
                total += order.Cost;

            return total;
        }
        #endregion

        #region BaseObject overrides - Save, Update, etc
        public override void Save()
        {
            // Save links this is dependent on

            // Save this
            base.Save();

            // Save link dependent on this
            foreach (StarSystemInformation sysInfo in knownStarSystems.Values)
                sysInfo.Save();
            //Should not need to save Owned as it is a subset of Known, same for HomeSystem
            //foreach (StarSystemInformation sysInfo in ownedStarSystems.Values)
            //    sysInfo.Save();
            //StarSystemInformation.SaveAllForRace(Id);
        }

        public static void SaveAll()
        {
            foreach (Race race in raceList.Values)
            {
                race.Save();
            }
        }

        internal override void Update()
        {
            if (MarkedToDelete)
            {
                // Delete dependencies

                DeleteNow();
            }
            else //if(IsNew || IsDirty)
            {
                if (IsNew)
                {
                    if (raceList == null) raceList = new Dictionary<Guid, Race>();
                    //raceList.Add(this.Id, this);
                }

                SaveValues();
                dbRace.SaveToDB(IsNew);
            }
        }

        internal override void DeleteNow()
        {
            if (dbRace.Values == null)
                dbRace.Values = new Stitch.Database.DBRow();

            dbRace.Values["id"] = Id;

            dbRace.Delete();

            //
            raceList.Remove(Id);
            MarkDeleted();
        }

        internal override void SaveValues()
        {
            if (dbRace.Values == null)
                dbRace.Values = new Stitch.Database.DBRow();

            dbRace.Values["id"] = Id;
            dbRace.Values["gameId"] = Game.ActiveGame.Id;
            dbRace.Values["playerId"] = PlayerId;
            dbRace.Values["name"] = Name;
            dbRace.Values["colour"] = Colour.ToArgb();
            dbRace.Values["habIndex"] = HabIndex;
            dbRace.Values["enviroment"] = EnumUtils.StringValueOf(PerferedEnviroment);
            dbRace.Values["economicLevel"] = EconomicLevel;
            dbRace.Values["initialTurn"] = InitialTurn.ToString();
            dbRace.Values["monthlyMcTotal"] = (decimal)MonthlyMcTotal;
            dbRace.Values["maintenanceCosts"] = (decimal)MaintenanceCosts;
            dbRace.Values["turnComplete"] = TurnComplete.ToString();
        }

        internal override void LoadValues()
        {
            if (dbRace.Values == null)
                return;

            Id = dbRace.Values["id"];
            PlayerId = dbRace.Values["playerId"];
            Name = dbRace.Values["name"];
            Colour = Color.FromArgb(dbRace.Values["colour"]);
            HabIndex = dbRace.Values["habIndex"];
            PerferedEnviroment = (SystemBodyEnviroment)EnumUtils.EnumValueOf(dbRace.Values["enviroment"], typeof(SystemBodyEnviroment));
            EconomicLevel = dbRace.Values["economicLevel"];
            InitialTurn = string.Compare(dbRace.Values["initialTurn"], "True") == 0;
            MonthlyMcTotal = (double)dbRace.Values["monthlyMcTotal"];
            MaintenanceCosts = (double)dbRace.Values["maintenanceCosts"];
            TurnComplete = string.Compare(dbRace.Values["turnComplete"], "True") == 0;
        }

        internal override void InitialiseObject()
        {
            // Force the loading of the related home system so the initial map is correct.
            StarSystem star = StarSystem.GetStarSystem(HomeSystem.SystemId);
        }
        #endregion

        public override string ToString()
        {
            return Name;
        }

        protected virtual void OnRaiseActiveRaceChangedEvent(BasicEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<BasicEventArgs> handler = RaiseActiveRaceChangedEvent;

            // Event will be null if there are no subscribers
            if (handler != null)
            {
                e.Ident = this.Id;
                e.EventType = BasicEventType.eRACE;
                e.Operation = BasicOperationType.eMODIFY;
             
                // Use the () operator to raise the event.
                handler(this, e);
            }
        }

        protected virtual void OnRaiseRaceChangedEvent(BasicEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<BasicEventArgs> handler = RaiseRaceChangedEvent;

            // Event will be null if there are no subscribers
            if (handler != null)
            {
                // Use the () operator to raise the event.
                handler(this, e);
            }
        }

        protected virtual void OnRaiseNewStarSystemEvent(BasicEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<BasicEventArgs> handler = RaiseNewStarSystemEvent;

            // Event will be null if there are no subscribers
            if (handler != null)
            {
                // Use the () operator to raise the event.
                handler(this, e);
            }
        }

        protected virtual void OnRaiseNewOrderEvent(BasicEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<BasicEventArgs> handler = RaiseNewOrderEvent;

            // Event will be null if there are no subscribers
            if (handler != null)
            {
                // Use the () operator to raise the event.
                handler(this, e);
            }
        }
    }
}
